/*
  ***** BEGIN LICENSE BLOCK *****
   The contents of this file are subject to the Mozilla Public License
   Version 1.1 (the "License"); you may not use this file except in
   compliance with the License. You may obtain a copy of the License at
   http://www.mozilla.org/MPL/
   
   Software distributed under the License is distributed on an "AS IS"
   basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
   License for the specific language governing rights and limitations
   under the License.
   
   The Original Code is BS1.
   
   The Initial Developer of the Original Code is SpaceMan.
   Portions created by SpaceMan are Copyright (C) 2009
   SpaceMan. All Rights Reserved.
   
   Contributor(s):
    SpaceMan <sandaimespaceman@gmail.com>
   
   Alternatively, the contents of this file may be used under the terms
   of the GNU Public License (the "GPL License"), in which case the
   provisions of MPL License are applicable instead of those
   above. If you wish to allow use of your version of this file only
   under the terms of the GPL License and not to allow others to use
   your version of this file under the MPL, indicate your decision by
   deleting the provisions above and replace them with the notice and
   other provisions required by the GPL License. If you do not delete
   the provisions above, a recipient may use your version of this file
   under either the MPL or the GPL License.
  ***** END LICENSE BLOCK *****
*/


/**
 * BrainSpace 1.0
 *  by SpaceMan
 *  This script is public domain.
 * @fileoverview BrainSpace 1.0's main file
 * @version 1.0
 * @author SpaceMan
 */

// --------------------------------------------------------

/* ---------------------------- *\
  BrainSpace 1.0 Specification:
  Operators:
	^ move up
	V v move down
	v move down
	> move right
	< move left
	% reverse
	* If current cell is not 0, go to next, otherwise, skip next
	X x end program
	/ bounce up/left
	\ bounce down/right
	? If current cell is 0, continue, otherwise, reflect back
	o O 0 output like putchar()
	i I 1 input like getchar()
	
	l L { decrease mem. pointer
	e E } increase mem. pointer
	p P + +1 for current cell
	m M - -1 for current cell
\* ---------------------------- */

/**
 * Creates a new BrainSpace 1.0 context
 * @class A BrainSpace 1.0 context.
 * @constructor BrainSpace1
 * @param code {string} The complete code
 * @param input_callback {function} The callback to prompt input
 * @param output_callback {function} The callback to print string
 * @param debug_callback {function} The callback to debug
 */
function BrainSpace1(code, input_callback, output_callback, debug_callback)
{
	/**
	 * The current direction
	 * @type int
	 */
	this.direction = this.DIRECTION.RIGHT; //direction to go
	/**
	 * The current location as [y,x] array
	 * @type int[]
	 */
	this.location = [0, 0]; //current location
	/**
	 * The memory cells
	 * @type int[]
	 */
	this.memory = [0]; //the memory cells
	/**
	 * The current memory pointer
	 * @type int
	 */
	this.memorypointer = 0; //the memory pointer

	/**
	 * The width of the code
	 * @type int
	 */
	this.maxwidth = 0;
	/**
	 * The BrainSpace code to be processed.
	 * @member BrainSpace1
	 * @type string[][]
	 */
	this.code = code || "";
	// explode code to grid
	this.code = this.code.split("\n");
	//get width
	for (var i = 0; i < this.code.length; i ++)
	{
		this.code[i] = this.code[i].split("");
		if (this.code[i].length > this.maxwidth)
		{
			this.maxwidth = this.code[i].length;
		}
	}

	//explode again
	for (var j = 0; j < this.code.length; j ++)
	{
		for (var k = 0; k < this.maxwidth; k ++)
		{
			if (!this.code[j][k])
			{
				this.code[j][k] = " ";
			}
		}
	}

	/**
	 * The function will be called for input prompt
	 * @member BrainSpace1
	 * @type function
	 */
	this.input_callback = input_callback || this.EXAMPLE_INPUT_CALLBACK;
	/**
	 * The function will be called for output
	 * @member BrainSpace1
	 * @type function
	 */
	this.output_callback = output_callback || this.EXAMPLE_OUTPUT_CALLBACK;
	/**
	 * The function will be called for debug
	 * The args for the function will be:
	 *   (int direction, int[] location, String[] memory, int mempointer)
	 * @member BrainSpace1
	 * @type function
	 */
	this.debug_callback = debug_callback || function(){};
}

BrainSpace1.prototype =
{
	/**
	 * An example input callback
	 * @member BrainSpace1
	 */
	EXAMPLE_INPUT_CALLBACK: function(msg)
	{
		return prompt(msg || "");
	},
	/**
	 * An example output callback
	 * @member BrainSpace1
	 * @type string
	 */
	EXAMPLE_OUTPUT_CALLBACK: function(msg)
	{
		alert(msg);
	},

	/**
	 * Constants for directions
	 * @member BrainSpace1
	 */
	DIRECTION:
	{
		/** Up */
		UP: 0,
		/** Right */
		RIGHT: 1,
		/** Down */
		DOWN: 2,
		/** Left */
		LEFT: 3
	},
	/**
	 * Join everything to string
	 * @returns {string} The complete plain-text source code.
	 */
	toString: function()
	{
		var ret = "";
		for (i in this.code)
		{
			ret += this.code[i] + "\n";
		}
		return ret;
	},
	/**
	 * Execute the BrainSpace code.
	 */
	run: function()
	{
		this.memorypointer = 0;
		this.memory = [0];
		this.direction = this.DIRECTION.RIGHT;
		this.location = [0, 0];

		var codechar = " ";
		
		while (true)
		{
			if (!this.go())
			{
				break;
			}
		}
	},
	/**
	 * Go to next block
	 */
	go: function()
	{
		//fix memory bug
		if (typeof this.memory[this.memorypointer] == 'undefined')
		{
			this.memory[this.memorypointer] = 0;
		}

		//get current opcode
		var codechar = " ";
		try
		{
			codechar = this.code[this.location[0] ][this.location[1] ];
		}
		catch(e)
		{
			
		}
		if (!codechar)
		{
			codechar = " ";
		}
		// get current code block
		switch(codechar)
		{
			//change directions
			case "^":
				this.direction = this.DIRECTION.UP;
				break;
			case "v":
			case "V":
				this.direction = this.DIRECTION.DOWN;
				break;
			case "<":
				this.direction = this.DIRECTION.LEFT;
				break;
			case ">":
				this.direction = this.DIRECTION.RIGHT;
				break;
			//bounce operator
			case "\\":
				switch(this.direction)
				{
					case this.DIRECTION.UP:
						this.direction = this.DIRECTION.LEFT;
						break;
					case this.DIRECTION.RIGHT:
						this.direction = this.DIRECTION.DOWN;
						break;
					case this.DIRECTION.DOWN:
						this.direction = this.DIRECTION.RIGHT;
						break;
					case this.DIRECTION.LEFT:
						this.direction = this.DIRECTION.UP;
						break;
					default:
						this.direction = this.DIRECTION.RIGHT;
						break;
				}
				break;
			case "/":
				switch(this.direction)
				{
					case this.DIRECTION.UP:
						this.direction = this.DIRECTION.RIGHT;
						break;
					case this.DIRECTION.RIGHT:
						this.direction = this.DIRECTION.UP;
						break;
					case this.DIRECTION.DOWN:
						this.direction = this.DIRECTION.LEFT;
						break;
					case this.DIRECTION.LEFT:
						this.direction = this.DIRECTION.DOWN;
						break;
					default:
						this.direction = this.DIRECTION.RIGHT;
						break;
				}
				break;
			//reverse direction
			case "%":
				switch(this.direction)
				{
					case this.DIRECTION.UP:
						this.direction = this.DIRECTION.DOWN;
						break;
					case this.DIRECTION.RIGHT:
						this.direction = this.DIRECTION.LEFT;
						break;
					case this.DIRECTION.DOWN:
						this.direction = this.DIRECTION.UP;
						break;
					case this.DIRECTION.LEFT:
						this.direction = this.DIRECTION.RIGHT;
						break;
					default:
						this.direction = this.DIRECTION.RIGHT;
						break;
				}
				break;
			//reflect like % when current memory cell is not 0
			case "?":
				if (this.memory[this.memorypointer])
				{
					switch(this.direction)
					{
						case this.DIRECTION.UP:
							this.direction = this.DIRECTION.DOWN;
							break;
						case this.DIRECTION.RIGHT:
							this.direction = this.DIRECTION.LEFT;
							break;
						case this.DIRECTION.DOWN:
							this.direction = this.DIRECTION.UP;
							break;
						case this.DIRECTION.LEFT:
							this.direction = this.DIRECTION.RIGHT;
							break;
						default:
							this.direction = this.DIRECTION.RIGHT;
							break;
					}
				}
				break;
			//skip 2 steps if current memory cell is 0
			case "*":
				var skip = 1;
				if (this.memory[this.memorypointer])
				{
					skip = 0;
				}
				switch (this.direction)
				{
					case this.DIRECTION.UP:
						this.location[0] -= skip;
						break;
					case this.DIRECTION.RIGHT:
						this.location[1] += skip;
						break;
					case this.DIRECTION.DOWN:
						this.location[0] += skip;
						break;
					case this.DIRECTION.LEFT:
						this.location[1] -= skip;
						break;
					default:
						break;
				}
				delete skip;
				break;
			//there are 2 operators to end program: x and X
			case "X":
				return false;
			case "x":
				return false;
			//opcodes
			case "{":
			case "l":
			case "L":
				if (this.memorypointer > 0)
				{
					this.memorypointer --;
				}
				break;
			case "}":
			case "r":
			case "R":
				this.memorypointer ++;
				break;
			case "+":
			case "p":
			case "P":
				this.memory[this.memorypointer] ++;
				break;
			case "-":
			case "m":
			case "M":
				this.memory[this.memorypointer] --;
				break;
			case "o":
			case "O":
			case "0":
				this.output_callback(this.memory[this.memorypointer]);
				break;
			case "i":
			case "I":
			case "1":
				this.memory[this.memorypointer] = this.input_callback();
				break;
			default:
				break;
		} // switch(this.code[this.location[0], this.location[1] ])

		//move to next block
		switch (this.direction)
		{
			case this.DIRECTION.UP:
				if (this.location[0] > 0)
				{
					this.location[0] --;
				}
				else
				{
					return false;
				}
				break;
			case this.DIRECTION.RIGHT:
				if (this.location[1] <= this.code[this.location[0]].length)
				{
					this.location[1] ++;
				}
				else
				{
					return false;
				}
				break;
			case this.DIRECTION.DOWN:
				if (this.location[0] <= this.code.length)
				{
					this.location[0] ++;
				}
				else
				{
					return false;
				}
				break;
			case this.DIRECTION.LEFT:
				if (this.location[1] > 0)
				{
					this.location[1] --;
				}
				else
				{
					return false;
				}
				break;
			default:
				//fix direction
				this.direction = this.DIRECTION.RIGHT;
				break;
		} // switch (this.direction)
		return true;
		/*
			//debug messages
			console.log("----------------------------")
			console.log("x: " + this.location[1]);
			console.log("y: " + this.location[0]);
			console.log("memory: " + this.memory[this.memorypointer]);
			console.log(this.memory);
			console.log(this.code);
			console.log("location: " + this.memorypointer);
			console.log("direction: " + this.direction);
			console.log("code: " + codechar);
			this.debug_callback(this.direction, this.location, this.memory, this.memorypointer);
		*/
	}
}
//TODO test cases
